package com.zj;

import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

public class DataGenerator {
    // 百家姓
    private static final String[] SURNAMES = {
            "赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈",
            "褚", "卫", "蒋", "沈", "韩", "杨", "朱", "秦", "尤", "许",
            "何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏",
            "陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章",
            "云", "苏", "潘", "葛", "奚", "范", "彭", "郎", "鲁", "韦",
            "昌", "马", "苗", "凤", "花", "方", "俞", "任", "袁", "柳",
            "酆", "鲍", "史", "唐", "费", "廉", "岑", "薛", "雷", "贺",
            "倪", "汤", "滕", "殷", "罗", "毕", "郝", "邬", "安", "常",
            "乐", "于", "时", "傅", "皮", "卞", "齐", "康", "伍", "余",
            "元", "卜", "顾", "孟", "平", "黄", "和", "穆", "萧", "尹"
    };

    // 常用名字用字
    private static final String[] NAME_CHARS = {
            "伟", "强", "磊", "静", "丽", "勇", "军", "洋", "涛", "超",
            "秀英", "敏", "芳", "娜", "秀兰", "霞", "婷", "玉兰", "萍", "颖",
            "浩", "宇", "鹏", "杰", "斌", "琳", "丹", "玲", "雪", "慧",
            "建国", "建军", "建华", "国栋", "国庆", "建平", "国强", "爱国", "爱民", "志强",
            "明", "刚", "亮", "小", "大", "中", "红", "兰", "文", "武"
    };

    // 总数据量 100w
    private static final int TOTAL_DATA = 1_000_000;
    // 每个文件的数据量 10w
    private static final int DATA_PER_FILE = 100_000;
    // 文件数量  计算需要的文件数量 100w / 10w = 10，  Math.ceil()向上取整，确保所有数据都被写入文件
    private static final int FILE_COUNT = (int) Math.ceil((double) TOTAL_DATA / DATA_PER_FILE);

    // 统计信息
    // 总数据量
    // AtomicLong为了处理 多线程环境下的原子操作 和 内存可见性 问题
    //普通 long 的 ++ 或 -- 操作不是原子的，实际上分为多个步骤（读取、修改、写入）。多线程并发时可能导致数据竞争（race condition）。
    private static final AtomicLong totalCount = new AtomicLong(0);
    //改写成Long型
    private static Long totalCount2 = 0L;
    private static Long totalCount3 = 0L;
    // 姓张的数量
    private static final AtomicInteger zhangCount = new AtomicInteger(0);
    // 王的数量
    private static final AtomicInteger wangCount = new AtomicInteger(0);
    // 李的数量
    private static final AtomicInteger liCount = new AtomicInteger(0);
    // 每个文件的数据量统计 <key:文件索引; value:数据量>
    private static final ConcurrentHashMap<Integer, Integer> fileDataCount = new ConcurrentHashMap<>();
    //已生成的id集合，用于去重，保证id唯一
    private static final Set<Long> idSet = new ConcurrentHashSet<>();

    // 雪花算法实例，用于生成唯一ID，  参数1：工作机器ID，参数2：数据中心ID
    private static final Snowflake snowflake = IdUtil.getSnowflake(1, 1);

    public static void main(String[] args) throws InterruptedException, ExecutionException {


        // 创建线程池  创建固定大小的线程池（处理器核心数相同），线程池中已经包含了线程
        //其线程数量与当前机器的处理器核心数相同
        ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());


        //方案一：Future
        //  任务列表（用于获取执行结果）
        //Future<?>：表示异步计算的结果，用于获取任务执行状态或返回值
        //futures 列表：存储所有任务的 Future 对象，便于后续统一管理（如检查完成状态、获取结果、取消任务等）。
//        List<Future<?>> futures = new ArrayList<>();
//        long startTime = System.currentTimeMillis();
//        // 提交文件生成任务（每个文件一个任务）
//        for (int i = 0; i < FILE_COUNT; i++) {
//            final int fileIndex = i;  // 文件索引（从0开始）
//
//            // 提交任务到线程池
//            //Future<?>表示一个异步计算的结果容器。  <?> 通配符表示任务可能无返回值（Runnable）或返回任意类型（Callable）。
//            Future<?> future = executor.submit(() ->
//                    generateDataForFile(fileIndex)  //生成指定文件的数据
//            );
//            futures.add(future);
//        }
//
//        // 等待所有任务完成
//        for (Future<?> future : futures) {
//            future.get();  //当调用 future.get() 时，当前线程（通常是主线程）会阻塞，直到该 Future 关联的任务执行完毕。
//            //如果任务已经完成，get() 会立即返回；如果任务仍在运行，调用线程会一直等待。
//            //如果不调用 get()，主线程可能在子任务未完成时就继续执行（甚至关闭线程池），导致数据不完整。
//        }
//        //关闭线程池
//        executor.shutdown();

        //方案二 CountDownLatch
        CountDownLatch latch = new CountDownLatch(FILE_COUNT); // 初始化计数器
        long startTime = System.currentTimeMillis();
        try {

            // 提交所有任务，每个任务完成后调用 latch.countDown()
            for (int i = 0; i < FILE_COUNT; i++) {
                final int fileIndex = i;
                executor.submit(() -> {
                    try {
                        generateDataForFile(fileIndex);
                    } finally {
                        latch.countDown(); // 任务完成，计数器减1
                    }
                });
            }

            // 主线程等待所有子线程任务完成
            latch.await(); // 阻塞直到计数器归零
            System.out.println("所有文件生成完成！");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 恢复中断状态
            System.err.println("主线程被中断: " + e.getMessage());
        } finally {
            executor.shutdown(); // 关闭线程池
        }


        //方案三:join  未使用线程池
//        List<Thread> threads = new ArrayList<>();
//        long startTime = System.currentTimeMillis();
//        // 提交文件生成任务（每个文件一个任务）
//        for (int i = 0; i < FILE_COUNT; i++) {
//            final int fileIndex = i;  // 文件索引（从0开始）
//
//            // 提交任务到线程池
//            Thread thread = new Thread(() ->
//                    generateDataForFile(fileIndex)  //生成指定文件的数据
//            );
//            threads.add(thread);
//            thread.start();
//        }
//
//        // 等待所有任务完成
//        for (Thread thread : threads) {
//            thread.join();  //阻塞直到任务完成，通过将每个 Thread 对象存入 threads 列表，主线程可以遍历该列表并调用 thread.join()，确保所有子线程执行完毕后主线程才继续。
//            //如果没有集合保存线程对象，主线程将无法追踪哪些线程需要等待。
//        }



        // 输出统计信息
        printStatistics();

        // 计算并输出总耗时
        long endTime = System.currentTimeMillis();
        System.out.printf("任务完成，耗时: %d 毫秒%n", endTime - startTime);
    }

    /**
     * 为指定文件生成数据
     * @param fileIndex 文件索引（0-based）
     */
    private static void generateDataForFile(int fileIndex) {
        // 生成文件名（a1.txt, a2.txt,...）
        String fileName = "a" + (fileIndex + 1) + ".txt";

        // 当前文件的数据行缓存
        List<String> lines = new ArrayList<>(DATA_PER_FILE);

        // 生成指定数量的数据行
        for (int i = 0; i < DATA_PER_FILE; i++) {
            // 检查是否已达到总数据量限制  确保不超过总数据量
            if (totalCount.get() >= TOTAL_DATA) {
                break;  // 达到限制 提前终止
            }

            // 生成唯一ID（使用雪花算法）
            long id = snowflake.nextId();

            // 检查ID是否唯一
            if (!idSet.add(id)) {
                i--;   // ID重复，重试当前迭代
                continue;
            }

            // 生成随机姓名
            String name = generateRandomName();

            // 统计姓氏
            countSurname(name);

            // 构建数据行（格式：编号,人名）   添加到文件内容
            lines.add(id + "," + name);

            // 更新总计数器
            totalCount.incrementAndGet();

//            synchronized (AtomicLong.class) {
//                totalCount2++;
//            }
            synchronized (Long.class) {
                totalCount2++;
            }
            totalCount3++;

        }

        // 将数据写入文件（使用Hutool工具类）
        //lines：要写入的字符串集合
        //file：目标文件对象
        //将集合中的每个元素作为一行写入文件。
        //使用 UTF-8 编码，覆盖文件原有内容（若文件已存在）。
        //自动关闭文件流，无需手动处理资源。
        //FileUtil.writeUtf8Lines() 方法会自动创建文件（如果不存在），并将数据写入其中。
        FileUtil.writeUtf8Lines(lines, new File(fileName));

        // 记录当前文件的数据量
        fileDataCount.put(fileIndex, lines.size());
    }


    /**
     * 生成随机中文姓名
     * @return 随机生成的姓名（姓氏+1-2个字的名字）
     */
    private static String generateRandomName() {
        // 随机选择姓氏
        String surname = RandomUtil.randomEle(SURNAMES);

        // 随机生成名字 (1-2个字)
        int nameLength = RandomUtil.randomInt(1, 3);
        StringBuilder nameBuilder = new StringBuilder(surname);

        // 构建姓名
        for (int i = 0; i < nameLength; i++) {
            nameBuilder.append(RandomUtil.randomEle(NAME_CHARS));
        }

        return nameBuilder.toString();
    }

    /**
     * 统计指定姓名的姓氏
     * @param name 待统计的姓名
     */
    private static void countSurname(String name) {
        // 检查是否以特定姓氏开头
        if (name.startsWith("张")) {
            zhangCount.incrementAndGet();
        } else if (name.startsWith("王")) {
            wangCount.incrementAndGet();
        } else if (name.startsWith("李")) {
            liCount.incrementAndGet();
        }
    }

    /**
     * 打印统计信息
     */
    private static void printStatistics() {
        System.out.println("========== 数据生成统计 ==========");
        System.out.println("总数据量: " + totalCount.get());
        System.out.println("totalCount2: " + totalCount2);
        System.out.println("totalCount3: " + totalCount3);
        System.out.println("zhangCount: " + zhangCount.get());
        System.out.println("wangCount: " + wangCount.get());
        System.out.println("liCount: " + liCount.get());

        System.out.println("\n各文件数据量:");
        fileDataCount.forEach((fileIndex, count) -> {
            System.out.printf("a%d.txt: %d 条数据%n", fileIndex + 1, count);
        });

    }
}
